﻿namespace NTLib.Core.Shared
{
    using NTLib.Core.Args;
    using NTLib.Core.Exceptions.Shared;
    using NTLib.Core.Manipulators;
    using NTLib.Core.Notifier;
    using NTLib.Core.Threading;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Text;
    using System.Threading.Tasks;

    /// <summary>
    /// Shared object used to manipulate SharedProperty values
    /// </summary>
    /// <typeparam name="Locker">The type of the locker.</typeparam>
    public class SharedObject<Locker> : BaseNotifier<Locker>, ISharedObject
        where Locker : ILocker, new()
    {
        #region Fields

        private static Locker s_locker;
        private static Dictionary<ISharedValueKey, ISharedValue> s_properties;

        #endregion

        #region Ctor

        /// <summary>
        /// Initializes a new instance of the <see cref="SharedObject{Locker}"/> class.
        /// </summary>
        public SharedObject()
        {
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the properties.
        /// </summary>
        private static Dictionary<ISharedValueKey, ISharedValue> Properties
        {
            get
            {
                if (s_properties == null)
                    s_properties = StaticSharedContainer.GetStaticResources<Dictionary<ISharedValueKey, ISharedValue>>(typeof(SharedObject<Locker>));
                return s_properties;
            }
        }


        /// <summary>
        /// Gets the lock provider.
        /// </summary>
        private static Locker LockProvider
        {
            get
            {
                if (s_locker == null)
                    s_locker = StaticSharedContainer.GetStaticResources<Locker>(typeof(SharedObject<Locker>));
                return s_locker;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Clears the local value.
        /// </summary>
        /// <exception cref="SharedValueNoFoundException"></exception>
        public void ClearLocalValue(ISharedValueKey prop)
        {
            ISharedValue sharedValue = null;
            using (LockProvider.ScopeLock(true))
            {
                if (!Properties.TryGetValue(prop, out sharedValue))
                    throw new SharedValueNoFoundException(prop);
            }
            sharedValue.ClearLocalValue(this);
        }

        /// <summary>
        /// Gets the value.
        /// </summary>
        /// <exception cref="SharedValueNoFoundException"></exception>
        public T GetValue<T>(ISharedValueKey prop)
        {
            using (LockProvider.ScopeLock(true))
            {
                ISharedValue value = null;
                if (Properties.TryGetValue(prop, out value))
                    return value.GetValue<T>(this);
            }
            throw new SharedValueNoFoundException(prop);
        }

        /// <summary>
        /// Sets the value shared by all the instance that have the shared Property.
        /// </summary>
        /// <exception cref="SharedValueNoFoundException"></exception>
        public bool OverrideSharedValue<T>(ISharedValueKey prop, T value)
        {
            ISharedValue sharedValue = null;
            using (LockProvider.ScopeLock(true))
            {
                if (!Properties.TryGetValue(prop, out sharedValue))
                    throw new SharedValueNoFoundException(prop);
            }
            return sharedValue.OverrideSharedValue(value);
        }

        /// <summary>
        /// Sets the local value.
        /// </summary>
        /// <exception cref="SharedValueNoFoundException"></exception>
        public bool SetValue<T>(ISharedValueKey prop, T value)
        {
            ISharedValue sharedValue = null;
            using (LockProvider.ScopeLock(true))
            {
                if (!Properties.TryGetValue(prop, out sharedValue))
                    throw new SharedValueNoFoundException(prop);
            }
            return sharedValue.SetLocalValue(this, value);
        }

        /// <summary>
        /// Creates the shared property.
        /// </summary>
        /// <param name="prop">Expression that return the property that will be mapper to the SharedProperty.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <param name="valueChanged">The value changed delegate.</param>
        /// <returns></returns>
        public static ISharedValueKey CreateSharedProperty<T, U>(Expression<Func<U, T>> prop, T defaultValue = default(T), Action<ISharedValueKey, ValueChangedEventArgs<T>> valueChanged = null)
            where U : ISharedObject
        {
            var accessor = new SimpleAccessor<T>();
            accessor.Set(defaultValue, true);

            var sharedProp = new SharedProperty<Locker, T>(accessor, (prop.Body as MemberExpression).Member.Name, typeof(U));

            if (valueChanged != null)
                sharedProp.ValueChanged += valueChanged;

            using (LockProvider.ScopeLock())
            {
                Properties.Add(sharedProp.Key, sharedProp);
            }

            return sharedProp.Key;

        }

        #endregion
    }

    /// <summary>
    /// Simple shared object not thread safe
    /// </summary>
    [NotThreadSafe]
    public class SharedObject : SharedObject<NoLocker>
    {
    }
}
